package com.yuandian.business.monitor.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yuandian.api.business_monitor.po.EndToEndConfig;
import com.yuandian.api.business_monitor.po.QueryBusinessDetailParams;
import com.yuandian.api.business_monitor.po.QueryBusinessStepParams;
import com.yuandian.api.business_monitor.po.QueryEndToEndDetailParams;
import com.yuandian.api.business_monitor.vo.*;
import com.yuandian.api.management.entity.ConfReturnCode;
import com.yuandian.api.management.entity.ConfSegment;
import com.yuandian.api.management.feign.*;
import com.yuandian.api.management.vo.EndToEndNodeVo;
import com.yuandian.api.management.vo.EndToEndRelationshipVo;
import com.yuandian.bpm.common.core.constant.SecurityConstants;
import com.yuandian.bpm.common.core.util.R;
import com.yuandian.bpm.common.data.resolver.ParamResolver;
import com.yuandian.business.monitor.mapper.BusinessDetailMapper;
import com.yuandian.business.monitor.mapper.BusinessEndToEndDetailMapper;
import com.yuandian.business.monitor.mapper.StepDetailMapper;
import com.yuandian.business.monitor.service.IBusinessDetailService;
import com.yuandian.constant.ConfigKey;
import com.yuandian.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;

/**
 * 说明：业务详情Service实现
 *
 * @Author yanjun
 * @Date 2020/9/7 10:35
 */
@Slf4j
@Service
public class BusinessDetailServiceImpl implements IBusinessDetailService {

	@Autowired
	private RemoteStepService searchOptionService;

	@Autowired
	private RemoteReturnCodeService remoteReturnCodeService;

	@Autowired
	private BusinessEndToEndDetailMapper businessStepMapper;

	@Autowired
	private BusinessDetailMapper businessDetailMapper;
	@Autowired
	private RemoteSegmentService remoteSegmentService;
	@Autowired
	private RemoteComponentIpService remoteComponentIpService;
	@Autowired
	private RemoteEndToEndRelationshipService remoteEndToEndRelationshipService;
	@Autowired
	private RemoteExclusiveIndicatorService remoteExclusiveIndicatorService;
	@Autowired
	private StepDetailMapper stepDetailMapper;

	private static ExecutorService threadPool = ExecutorUtils.defaultThreadPool();


	/**
	 * 根据详单id查询业务详情
	 *
	 * @param params 业务详情查询参数
	 * @return 业务详情vo对象
	 */
	@Override
	public BusinessDetailVo queryBusinessDetailByDetailId(QueryBusinessDetailParams params) {
		BusinessDetailVo bdVo = new BusinessDetailVo();
		String businessDetailId = params.getBusinessDetailId();
		if (StringUtils.isNotEmpty(businessDetailId)) {
			bdVo = businessDetailMapper.selectById(params);
			if (ObjectUtil.isNotEmpty(bdVo)) {
				Map<String, List<String>> businessIndicatorsShowMap = remoteExclusiveIndicatorService.getBusinessIndicatorsShowMap().getData();
				String indicatorWithSet = ExclusiveIndicator.getIndicatorWithSet(bdVo.getExclusiveIndicator(), businessIndicatorsShowMap.get(bdVo.getBusinessCode()));
				bdVo.setExclusiveIndicator(indicatorWithSet);
			}
		}
		return bdVo;
	}

	/**
	 * 分页查询业务步骤列表
	 *
	 * @param params 查询业务步骤参数
	 * @return 通用分页对象
	 */
	@Override
	public PageDataInfo queryBusinessStepListPage(QueryBusinessStepParams params) {
		List<BusinessStepVo> bsVoList = new ArrayList<>();
		Map<String, String> resMap = new HashMap<>();

		try {
			long sTime = System.currentTimeMillis();
			R<Map<String, String>> result = searchOptionService.queryStepTypeByBusinessCode();
			log.info("[BPM模块]-[远程调用-后台配置服务]-[查询业务步骤类型]-耗时: {} MS", System.currentTimeMillis() - sTime);
			if (result != null && result.getData() != null) {
				resMap = result.getData();
			}
		} catch (Exception e) {
			throw new RuntimeException("[BPM模块]-[远程调用-后台配置服务]-[查询业务步骤类型]报错: {}" + e.getMessage());
		}

		// 默认按startTimeUsec升序
		params.setOrderByColumn("start_time_usec");
		params.setIsAsc("asc");
		IPage page = new Page(params.getPageNum(), params.getPageSize());
		bsVoList = businessStepMapper.queryBusinessStep(page, params);

		// 获取 自定义返回码注释 回填到前端
		Map<String, String> codeMap = getCodeExplain();
		String explain = "";
		// 3、处理结果
		for (BusinessStepVo businessStep : bsVoList) {
			explain = codeMap.get(businessStep.getSystemName() + "@" + businessStep.getCustomResCode());
			businessStep.setStepType(resMap.get(businessStep.getBusinessCode() + "@" + businessStep.getStepCode()));
			businessStep.setExplain(explain);
		}
		return new PageDataInfo(page.getTotal(), bsVoList);
	}

	public Map<String, String> getCodeExplain() {

		Map<String, String> resMap = new HashMap<>();
		ConfReturnCode params = new ConfReturnCode();
		// 4:自定返回码类型  3: 数据库返回码 2：中间件返回码  1：web 返回码
		params.setTypeId(4);
		try {
			long sTime = System.currentTimeMillis();
			R<List<ConfReturnCode>> result = remoteReturnCodeService.getCodeDetail(params);
			log.info("[BPM模块]-[远程调用-后台配置服务]-[获取自定义返回码信息]-耗时: {} MS", System.currentTimeMillis() - sTime);
			if (result != null) {
				result.getData()
						.forEach(returnCode -> {
							resMap.put(returnCode.getName() + "@" + returnCode.getValue(), returnCode.getExplain());
						});
			}
		} catch (Exception e) {
			throw new RuntimeException("[BPM模块]-[远程调用-后台配置服务]-[获取自定义返回码信息]报错: {}" + e.getMessage());
		}
		return resMap;
	}

	/**
	 * 查询端到端节点详情
	 *
	 * @param params 查询端到端节点详情数据参数
	 * @return 通用分页对象
	 */
	@Override
	public PageDataInfo queryEndToEndDetail(QueryEndToEndDetailParams params) {
		List<EndToEndDetailVo> voList = new ArrayList<>();
		Page page = new Page<>(params.getPageNum(), params.getPageSize());
		List<Map<String, Object>> list = null;
		String dataType = params.getDataType();

		// 查询数据
		list = queryEndToEndDetailData(params, page);

		if (list != null && list.size() > 0) {
			EndToEndDetailVo vo = null;
			String time = "";
			for (Map<String, Object> map : list) {
				vo = new EndToEndDetailVo();
				//数据id
				vo.setId(DataTypeUtil.objectToLong(map.get("id")));
				//数据id(字符串型)
				vo.setIdStr(DataTypeUtil.objectToString(map.get("id")));
				vo.setResource(DataTypeUtil.objectToString(map.get("request")));
				if ("web".equals(dataType)) {
					vo.setRequestUrl(DataTypeUtil.objectToString(map.get("request")));
				} else if ("mid".equals(dataType)) {
					vo.setApi(DataTypeUtil.objectToString(map.get("request")));
				} else if ("db".equals(dataType)) {
					vo.setSqlString(DataTypeUtil.objectToString(map.get("request")));
				} else {
					vo.setRequestUrl(DataTypeUtil.objectToString(map.get("request")));
				}
				//请求ip
				vo.setRequestIp(DataTypeUtil.objectToString(map.get("requestIp")));
				//请求端口
				vo.setRequestPort(DataTypeUtil.objectToInteger(map.get("requestPort")));
				//响应ip
				vo.setResponseIp(DataTypeUtil.objectToString(map.get("responseIp")));
				//响应端口
				vo.setResponsePort(DataTypeUtil.objectToInteger(map.get("responsePort")));
				//系统返回码
				vo.setServerResCode(DataTypeUtil.objectToInteger(map.get("serverResCode")));
				//业务状态
				vo.setDealState(DataTypeUtil.objectToInteger(map.get("dealState")));
				//响应时长
				vo.setServerResponseTime(DataTypeUtil.objectToLong(map.get("serverResponseTime")));
				//处理时长
				vo.setDealTime(DataTypeUtil.objectToLong(map.get("dealTime")));
				//请求开始时间
//	            time = DataTypeUtil.objectToString(map.get("startTime"));
				vo.setStartTime(YDateUtil.getTimeStr(((LocalDateTime) map.get("startTime"))));
				//响应结束时间
//	            time = DataTypeUtil.objectToString(map.get("endTime"));
				vo.setEndTime(YDateUtil.getTimeStr(((LocalDateTime) map.get("endTime"))));
				//客户端网络传输耗时
				vo.setClientTranslateTime(DataTypeUtil.objectToLong(map.get("clientTranslateTime")));
				//服务端网络传输耗时
				vo.setServerTranslateTime(DataTypeUtil.objectToLong(map.get("serverTranslateTime")));
				//告警数
				vo.setAlarmCount(DataTypeUtil.objectToInteger(map.get("alarmCount")));
				//专属指标
				Map<String, List<String>> resourcesIndicatorsShowMap = remoteExclusiveIndicatorService.getResourcesIndicatorsShowMap().getData();
				String exclusiveIndicator = DataTypeUtil.objectToString(map.get("exclusiveIndicator"));
				String resourceCode = DataTypeUtil.objectToString(map.get("resourceCode"));
				vo.setExclusiveIndicator(ExclusiveIndicator.getIndicatorWithSet(exclusiveIndicator, resourcesIndicatorsShowMap.get(resourceCode)));
				//探针IP及端口号    Ip:探针口编号
				vo.setProbeIp(DataTypeUtil.objectToString(map.get("probeIp")));
				//请求大小
				vo.setRequestBytes(DataTypeUtil.objectToLong(map.get("requestBytes")));
				//响应大小
				vo.setResponseBytes(DataTypeUtil.objectToLong(map.get("responseBytes")));
				voList.add(vo);
			}
		}
		return new PageDataInfo(page.getTotal(), voList);
	}

	/**
	 * 查询数据
	 *
	 * @param params 请求参数
	 * @param page   分页对象
	 * @return 结果列表
	 */
	public List<Map<String, Object>> queryEndToEndDetailData(QueryEndToEndDetailParams params, Page page) {
		List<Map<String, Object>> list = null;
		List<String> idList = new ArrayList<>();
		String dataType = params.getDataType();
		List<String> codeList = params.getCodeList();

		if (CollectionUtil.isNotEmpty(codeList)) {
			for (String code : codeList) {
				String[] strs = code.split("#");
				if (strs.length > 1) {
					idList.add(strs[1]);
				}
			}
		}

		if (params.getPageNum() == 0) {
			params.setPageNum(1);
		}
		if (params.getPageSize() == 0) {
			params.setPageSize(10);
		}

		switch (dataType) {
			case "web":
				list = businessStepMapper.selectBusinessEndToEndDetail(page, "dwd_business_web", "request_url", idList);
				break;
			case "mid":
				list = businessStepMapper.selectBusinessEndToEndDetail(page, "dwd_business_mid", "api", idList);
				break;
			case "db":
				list = businessStepMapper.selectBusinessEndToEndDetail(page, "dwd_business_db", "sql_string", idList);
				break;
			default:
				idList.add(params.getDataId());
				list = businessStepMapper.selectBusinessEndToEndDetail(page, "dwd_business_step", "request_url", idList);
				break;
		}

		return list;
	}


	/**
	 * 查询端到端网络流量信息
	 *
	 * @param params 查询端到端网络流量信息
	 * @return 通用分页对象
	 */
	@Override
	public PageDataInfo queryNetworkDetail(QueryEndToEndDetailParams params) {

		Page page = new Page<>(params.getPageNum(), params.getPageSize());
		List<EndToEndNetworkDetailVo> result = new ArrayList<>();

		// 查询 四元组、网端ID、请求时间、结束时间 数据并作为请求参数
		List<Map<String, Object>> maps = queryEndToEndDetailData(params, page);
		if (CollectionUtil.isNotEmpty(maps)) {
			Map<String, Object> map = maps.get(0);
			if (ObjectUtils.isNotEmpty(map.get("startTime"))) {
				params.setStartTime(YDateUtil.getTimeStr(((LocalDateTime) map.get("startTime"))));
			}
			if (ObjectUtils.isNotEmpty(map.get("endTime"))) {
				params.setEndTime(YDateUtil.getTimeStr(((LocalDateTime) map.get("endTime"))));
			}
			if (ObjectUtils.isNotEmpty(map.get("requestIp"))) {
				params.setRequestIp(IpUtils.ipToDecimal(map.get("requestIp").toString()) + "");
			}
			if (ObjectUtils.isNotEmpty(map.get("requestPort"))) {
				params.setRequestPort(map.get("requestPort").toString());
			}
			if (ObjectUtils.isNotEmpty(map.get("responseIp"))) {
				params.setResponseIp(IpUtils.ipToDecimal(map.get("responseIp").toString()) + "");
			}
			if (ObjectUtils.isNotEmpty(map.get("responsePort"))) {
				params.setResponsePort(map.get("responsePort").toString());
			}
			if (ObjectUtils.isNotEmpty(map.get("segmentId"))) {
				params.setSegmentId(map.get("segmentId").toString());
			}

			// 自定义查询分区
//			Set<String> partitionSet = new HashSet<>();
//			partitionSet.add(PartitionUtil.getPartitionBasedOnHouse(params.getStartTime()));
//			partitionSet.add(PartitionUtil.getPartitionBasedOnHouse(params.getEndTime()));
//			params.setPartitionSet(partitionSet);

			// 根据上面条件查询数据
			result = businessStepMapper.queryNetworkDetail(page, params);
		}

		return new PageDataInfo(page, result);
	}


	/**
	 * 端到端实时关联
	 *
	 * @param endToEndRelationVo 端到端关联vo对象
	 * @return 端到端关联展示集合
	 */
	@Override
	public List<EndToEndNodeVo> endToEndRelation(EndToEndRelationVo endToEndRelationVo) {
		String taskTitle = "[端到端实时关联-" + UUID.randomUUID().toString().replaceAll("-", "").toUpperCase() + "]";
		long taskStartTime = System.currentTimeMillis();
		log.info("{} start......", taskTitle);

		// 端到端实时关联配置对象
		EndToEndConfig config = new EndToEndConfig();
		getConfig(config, endToEndRelationVo);
		// 端到端关联展示集合
		List<EndToEndNodeVo> list = config.getEndToEndNodeVos();
		if (CollectionUtils.isEmpty(list)) {
			log.info("{} 展示数据为空，end......", taskTitle);
			return list;
		}
		taskStartTime = System.currentTimeMillis();

		// 4、获取、计算端详情数据
		Map<Integer, SegmentDetail> sdVoMap = computeSegmentDetail(endToEndRelationVo, config);
		log.info("{} 4.获取、计算端详情数据，耗时：{}ms", taskTitle, System.currentTimeMillis() - taskStartTime);
		if (CollectionUtils.isEmpty(sdVoMap)) {
			log.info("{} 端详情数据为空，end......", taskTitle);
			return list;
		}
		taskStartTime = System.currentTimeMillis();

		// 5、填充数据:根据展示的需要
		// 5.1、遍历端详情数据集
		sdVoMap.forEach((segmentId, sdVo) -> {
			// 设置开始结束时间
			String afterStartTime = sdVo.getAfterStartTime();
			String afterEndTime = sdVo.getAfterEndTime();
			sdVo.setStartTime(StringUtils.isEmpty(afterStartTime) ? sdVo.getStartTime() : afterStartTime);
			sdVo.setEndTime(StringUtils.isEmpty(afterEndTime) ? sdVo.getEndTime() : afterEndTime);
			// 设置处理时间
			Long startTimeUsec = sdVo.getStartTimeUsec();
			Long endTimeUsec = sdVo.getEndTimeUsec();
			Long afterStartTimeUsec = sdVo.getAfterStartTimeUsec();
			Long afterEndTimeUsec = sdVo.getAfterEndTimeUsec();
			if (afterStartTimeUsec != null && afterEndTimeUsec != null) {
				sdVo.setDealTime((float) (afterEndTimeUsec - afterStartTimeUsec));
			} else if (startTimeUsec != null && endTimeUsec != null) {
				sdVo.setDealTime((float) (endTimeUsec - startTimeUsec));
			}
		});
		// 5.2、设置端坐标和相关展示数据
		fillSegmentDetailData(list, sdVoMap);
		log.info("{} 5.根据展示的需要填充数据，耗时：{}ms", taskTitle, System.currentTimeMillis() - taskStartTime);
		log.info("{} success end......", taskTitle);
		return list;
	}

	/**
	 * 获取端到端关联相关配置
	 *
	 * @param config
	 * @param endToEndRelationVo
	 */
//	@Cacheable(value = RedisNameConst.MANAGEMENT_OPTION_BUSINESS, key = )
	private void getConfig(EndToEndConfig config, EndToEndRelationVo endToEndRelationVo) {
		String taskTitle = "[端到端实时关联-" + UUID.randomUUID().toString().replaceAll("-", "").toUpperCase() + "]";
		long taskStartTime = System.currentTimeMillis();
		config.setSystemId(endToEndRelationVo.getSystemId());
		// 设置配置参数
		String p1 = "0";// 默认关闭
		// 默认100ms
		String p2 = "100";
		try {
			String compleData = ParamResolver.getStr(ConfigKey.E2E_COMPLEDATA);
			if (ObjectUtil.isNotEmpty(compleData)) {
				p1 = compleData;
			}
		} catch (Exception e) {
			log.error("获取配置'business.detail.e2e.compleData'出错，异常：{}", e.getMessage());
		}
		try {
			String containTime = ParamResolver.getStr(ConfigKey.E2E_CONTAIN_TIME);
			if (ObjectUtil.isNotEmpty(containTime)) {
				p2 = containTime;
			}
		} catch (Exception e) {
			log.error("获取配置'business.detail.e2e.containTime'出错，异常：{}", e.getMessage());
		}
		config.setBusinessDetailInsertFlag(p1);
		config.setContainTime(Long.valueOf(p2));
		boolean stepFlag = true;//是否通过步骤查询
		// 1、获取端配置：如果有业务步骤直接获取业务步骤下的端配置
		String businessCode = endToEndRelationVo.getBusinessCode();//业务编码
		String stepCode = endToEndRelationVo.getStepCode();//业务步骤编码
		Integer systemId = endToEndRelationVo.getSystemId();//系统id
		List<EndToEndNodeVo> segmentInfoTreeList = new ArrayList<>();//端到端关系（树状）
		List<EndToEndNodeVo> EndToEndNodeList = new ArrayList<>();//端到端关系
		if (ObjectUtil.isNotEmpty(businessCode) && ObjectUtil.isNotEmpty(stepCode) && ObjectUtil.isNotEmpty(systemId)) {
			EndToEndRelationshipVo endToEndRelationshipVo = new EndToEndRelationshipVo();
			endToEndRelationshipVo.setSystemId(systemId);
			endToEndRelationshipVo.setBusinessCode(businessCode);
			endToEndRelationshipVo.setStepCode(stepCode);
			try {
				segmentInfoTreeList = remoteEndToEndRelationshipService.getEndToEndRelationship(endToEndRelationshipVo).getData();
			} catch (Exception e) {
				log.error("[BPM-端到端实时关联-根据系统、业务编码、业务步骤获取配置库端数据出错，错误原因：{}]", e);
				throw new RuntimeException("[BPM-端到端实时关联-根据系统、业务编码、业务步骤获取配置库端数据出错，错误原因：" + e.getMessage() + "]");
			}
			if (ObjectUtil.isEmpty(segmentInfoTreeList)) {
				log.info("通过系统id,业务编码，业务步骤获取配置库端数据为空！");
//				return list;
			}
			log.info("{} 1.通过系统id,业务编码，业务步骤获取配置库端数据，耗时：{}ms", taskTitle, System.currentTimeMillis() - taskStartTime);

		} else {
			stepFlag = false;
			try {
				segmentInfoTreeList = remoteEndToEndRelationshipService.getEndToEndRelationBySystemId(systemId).getData();
			} catch (Exception e) {
				log.error("[BPM-端到端实时关联-根据系统获取配置库端数据出错，错误原因：{}]", e);
				throw new RuntimeException("[BPM-端到端实时关联-根据系统获取配置库端数据出错，错误原因：" + e.getMessage() + "]");
			}
			log.info("{} 1.通过系统id获取配置库端数据，耗时：{}ms", taskTitle, System.currentTimeMillis() - taskStartTime);
		}
		taskStartTime = System.currentTimeMillis();
		// 2、获取端id与该端是否NAT、该端网络类型的映射(若为首端，额外保存映射0=NetworkType)
		Map<Integer, SegmentIdMappingVo> segmentIdMap = new HashMap<>();
		//根据大端类型分组<web端：resource>
		Map<Integer, List<String>> segmentTypeResourceCodeMap = new HashMap<>();
		//<当前资源编码， 后端节点>
		Map<String, List<EndToEndNodeVo>> resourceNdesMap = new HashMap<>();
		//整理成单层次数组顺便整理map
		toSingleLevel(segmentInfoTreeList, EndToEndNodeList, segmentIdMap, segmentTypeResourceCodeMap, resourceNdesMap);
		config.setSegmentIdMap(segmentIdMap);
		config.setSegmentTypeResourceCodeMap(segmentTypeResourceCodeMap);
		config.setResourceNodes(resourceNdesMap);
		log.info("{} 2.获取端id与该端是否NAT、该端网络类型的映射，耗时：{}ms", taskTitle, System.currentTimeMillis() - taskStartTime);
		taskStartTime = System.currentTimeMillis();

		/*
		 * 3、将端数据对象转成端到端展示vo对象集合(最终返回的对象)，
		 */
		config.setHasClient(true);
		config.setEndToEndNodeVos(changeEndToEndShowVo(segmentInfoTreeList, EndToEndNodeList, config));
		log.info("{} 3.将端数据对象转成端到端展示vo对象集合，耗时：{}ms", taskTitle, System.currentTimeMillis() - taskStartTime);
	}

	/**
	 * 填充网端数据
	 *
	 * @param segmentInfoTreeList
	 * @param sdVoMap
	 */
	private void fillSegmentDetailData(List<EndToEndNodeVo> segmentInfoTreeList, Map<Integer, SegmentDetail> sdVoMap) {
		for (EndToEndNodeVo endToEndNode : segmentInfoTreeList) {
			List<EndToEndNodeVo> childNodes = endToEndNode.getChildNodes();
			Integer segmentId = endToEndNode.getSegmentId();
			// 设置相关展示数据
			SegmentDetail sdVo = sdVoMap.get(segmentId);
			if (sdVo != null) {
				if (sdVo.getSumCount() == -1) {
					endToEndNode.setDealCount(-1);
				}
				endToEndNode.setDataId(sdVo.getDataId());
				endToEndNode.setDealTime(sdVo.getDealTime());
				endToEndNode.setServerResponseTime(sdVo.getServerResponseTime());
				if (sdVo.getSuccessCount() > 0) {
					endToEndNode.setState("成功");
				} else {
					endToEndNode.setState("失败");
				}
				endToEndNode.setShow(!(sdVo.getSumCount() == -1 || ObjectUtil.isEmpty(sdVo.getIpPort())));
				endToEndNode.setIpPort(sdVo.getIpPort());
				endToEndNode.setDataType(sdVo.getDataType());
				endToEndNode.setHeaderFlag(sdVo.getHeaderFlag());
				endToEndNode.setSuccessRate(sdVo.getSuccessRate());
				endToEndNode.setRequestMac(sdVo.getRequestMac());
				endToEndNode.setResponseMac(sdVo.getResponseMac());
				endToEndNode.setPreRelvanceIds(sdVo.getPreRelvanceIds());
				endToEndNode.setResourceCodes(sdVo.getResourceCodes());
				endToEndNode.setAverageNetTransTime(sdVo.getAverageNetTransTime());
				endToEndNode.setStartTimeUsec(sdVo.getStartTimeUsec());
				endToEndNode.setEndTimeUsec(sdVo.getEndTimeUsec());
				endToEndNode.setOneFlag(sdVo.getOneFlag());
				endToEndNode.setStartTime(sdVo.getStartTime());
				endToEndNode.setEndTime(sdVo.getEndTime());
				endToEndNode.setClientTransTime(Objects.isNull(sdVo.getClientTransTime()) ? null : (sdVo.getClientTransTime() / 1000));
				endToEndNode.setServerTransTime(Objects.isNull(sdVo.getServerTransTime()) ? null : (sdVo.getServerTransTime() / 1000));
				endToEndNode.setServerResponseStartTime(sdVo.getServerResponseStartTime());
				endToEndNode.setServerResponseEndTime(sdVo.getServerResponseEndTime());
				if (ObjectUtil.isNotEmpty(childNodes)) {
					for (EndToEndNodeVo childNode : childNodes) {
						Integer childSegmentId = childNode.getSegmentId();
						SegmentDetail afterSegmentDetailVo = sdVoMap.get(childSegmentId);
						if (ObjectUtil.isNotEmpty(afterSegmentDetailVo) && afterSegmentDetailVo.getSegmentId().equals(childSegmentId)) {
							//将后一端开始结束时间存到前一端
							endToEndNode.setAfterStartTime(afterSegmentDetailVo.getStartTime());
							endToEndNode.setAfterEndTime(afterSegmentDetailVo.getEndTime());
							endToEndNode.setAfterStartTimeUsec(afterSegmentDetailVo.getStartTimeUsec());
							endToEndNode.setAfterEndTimeUsec(afterSegmentDetailVo.getEndTimeUsec());
						}
					}
					fillSegmentDetailData(childNodes, sdVoMap);
				}
			} else {
				endToEndNode.setShow(false);
			}

		}

	}

	/**
	 * 将树状结构整理成单层次结构数组
	 *
	 * @param endToEndNodeVos            树状结构
	 * @param segmentInfoList
	 * @param segmentIdMap
	 * @param segmentTypeResourceCodeMap <网端类型, 资源编码集合>
	 */
	private void toSingleLevel(List<EndToEndNodeVo> endToEndNodeVos, List<EndToEndNodeVo> segmentInfoList, Map<Integer,
			SegmentIdMappingVo> segmentIdMap, Map<Integer, List<String>> segmentTypeResourceCodeMap, Map<String, List<EndToEndNodeVo>> resourceNodesMap) {
		if (ObjectUtil.isNotEmpty(endToEndNodeVos)) {
			for (EndToEndNodeVo endToEndNodeVo : endToEndNodeVos) {
				String resourceCode = endToEndNodeVo.getResourceCode();
				//1、单层结构
				segmentInfoList.add(endToEndNodeVo);
				//2、整理成<resourceCode, List<后端节点>>
				resourceNodesMap.put(resourceCode, endToEndNodeVo.getChildNodes());
				//3、收集网端网络类型
				ConfSegment segmentInfo = endToEndNodeVo.getSegmentInfo();
				if (ObjectUtil.isEmpty(segmentInfo)) {
					continue;
				}
				SegmentIdMappingVo simVo = new SegmentIdMappingVo();
				simVo.setIsNat(segmentInfo.getIsNat());
//			simVo.setNetworkType(segmentInfo.getNetworkType()); todo 暂时没有
				segmentIdMap.put(segmentInfo.getId(), simVo);
				//4、收集资源编码
				Integer type = segmentInfo.getType();
				List<String> list = segmentTypeResourceCodeMap.get(type);
				if (ObjectUtil.isEmpty(list)) {
					list = new ArrayList<>();
					list.add(resourceCode);
					segmentTypeResourceCodeMap.put(type, list);
				} else {
					list.add(resourceCode);
				}
				// 若为首端，额外保存映射0=NetworkType，多首端情况下未考虑
				if (segmentInfo.getSign() == 1) {
					SegmentIdMappingVo exactSimVo = new SegmentIdMappingVo();
//				exactSimVo.setNetworkType(segmentInfo.getNetworkType());
					segmentIdMap.put(0, exactSimVo);
				}
				List<EndToEndNodeVo> childNodes = endToEndNodeVo.getChildNodes();
				if (ObjectUtil.isNotEmpty(childNodes)) {
					toSingleLevel(childNodes, segmentInfoList, segmentIdMap, segmentTypeResourceCodeMap, resourceNodesMap);
				}
			}
		}

	}

	/**
	 * 整理端到端展示vo对象集合(最终返回的对象)
	 *
	 * @param segmentInfoList  端数据对象集合树状结构
	 * @param endToEndNodeList 端数据对象集合单层结构
	 * @param config           端到端实时关联配置对象
	 * @return 端到端展示vo对象
	 */
	private List<EndToEndNodeVo> changeEndToEndShowVo(List<EndToEndNodeVo> segmentInfoList, List<EndToEndNodeVo> endToEndNodeList, EndToEndConfig config) {
		// 是否配置了客户端
		boolean clientFlag = false;
//		Set<Integer> segmentIds = config.getSegmentIdMap().keySet();
//		ConfComponentIpParam confComponentIpParam = new ConfComponentIpParam();
//		confComponentIpParam.setSegmentIds(segmentIds);
//		confComponentIpParam.setSystemId(config.getSystemId());
//		List<ConfComponentIp> confComponentIps = remoteComponentIpService.queryBySegmentIds(confComponentIpParam).getData();
//		//根据网端id进行分组，收集ip端口<网端id:[ip端口]>
//		Map<Integer, List<String>> ipPortMap = confComponentIps.stream().collect(Collectors.groupingBy(ConfComponentIp::getSegmentId, Collectors.mapping(e -> e.getIp() + ":" + e.getPort(), Collectors.toList())));
		List<EndToEndNodeVo> endToEndNodeVos = new ArrayList<>();
		// 1、如果要展示客户端并且客户端无配置，那么手动新增客户端配置
		if (config.isHasClient() && !clientFlag) {
			EndToEndNodeVo endToEndShowVo = new EndToEndNodeVo();
			endToEndShowVo.setSegmentName("客户端");
			endToEndShowVo.setSegmentId(0);
			ConfSegment confSegment = new ConfSegment();
			confSegment.setId(0);
			confSegment.setName("客户端");
			confSegment.setTypeName("客户端");
			endToEndShowVo.setSegmentInfo(confSegment);
			endToEndShowVo.setChildNodes(segmentInfoList);
			endToEndNodeVos.add(endToEndShowVo);
			return endToEndNodeVos;
		} else {
			return segmentInfoList;
		}
	}

	/**
	 * 计算获取端详情数据
	 *
	 * @param endToEndRelationVo 端到端关联vo对象
	 * @param config             端到端实时关联配置对象
	 * @return 端id与端详情vo对象的映射关系
	 */
	private Map<Integer, SegmentDetail> computeSegmentDetail(EndToEndRelationVo endToEndRelationVo, EndToEndConfig config) {
		String taskTitle = "[端到端实时关联-" + UUID.randomUUID().toString().replaceAll("-", "").toUpperCase() + "]";
		// 端id与端详情vo对象的映射关系
		Map<Integer, SegmentDetail> sdVoMap = new HashMap<>();
		// 关联类型
		Integer relationType = endToEndRelationVo.getType();
		if (relationType == 1) {
			// 加载项端到端关联
		} else if (relationType == 2) {
			// 步骤端到端关联
			/*
			 * 1、根据preRelvanceId(如：69529506220370233)查询业务步骤1表(如：business_step_20200910中id=69529506220370233)，
			 *    得到的业务步骤对象是首端的数据
			 */
			String preRelvanceId = endToEndRelationVo.getPreRelvanceId();
			BusinessStep headBusinessStep = queryBusinessStepById(preRelvanceId, endToEndRelationVo.getDetailId());

			// 2、设置客户端和首端数据，客户端的端id为0，详细数据沿用首端数据(P.S：客户端和首端在每个端到端关联图中都有的)
			if (headBusinessStep == null) {
				log.info("{}_4: 实时关联失败，找不到首端，传过来的首端步骤Id{}", taskTitle, preRelvanceId);
				return sdVoMap;
			}
			sdVoMap = fillHeadData(headBusinessStep);

			// 3、根据首端数据进行查询关联数据
			Map<Integer, Map<String, List<SegmentDataVo>>> allSegmentData = queryRelevanceData(headBusinessStep, config);

			// 4、端到端关联递归筛选、数据补全:挑选出能关联上得端详情数据
			List<SegmentDataVo> list = recursiveScreen(
					headBusinessStep, allSegmentData, config, new ArrayList<>(), new ArrayList<>(), false);

			// 5、计算、合并结果集
			if (CollectionUtils.isEmpty(list)) {
				log.info("{}_4 递归筛选后结果集为空", taskTitle);
				return sdVoMap;
			}
			for (SegmentDataVo sdVo : list) {
				Integer segmentId = sdVo.getSegmentId();
				// 5.1、填充没数据，但要展示的端
				if (StringUtils.isEmpty(sdVo.getId())) {
					SegmentDetail detail = new SegmentDetail();
					detail.setSegmentId(segmentId);
					detail.setResourceCodes(new ArrayList<>(Arrays.asList("0")));
					detail.setSumCount(-1L);
					sdVoMap.put(segmentId, detail);
					continue;
				}
				// 5.2、计算各指标的数据
				SegmentDetail res = sdVoMap.get(segmentId);
				if (res != null && res.getSumCount() != -1) {
					Long sumCount = res.getSumCount() + 1;
					Integer successCount = sdVo.getState() == 1 ? 1 : 0;
					Integer healthCount = res.getHealthCount() + successCount;
					// 如果第一条是失败则都为失败
					if (res.getSuccessCount() == 0) {
						successCount = 0;
					}
					Float sumNetTime = sdVo.getNetTime() + res.getSumCount() * res.getAverageNetTransTime();
					Float clientTranslateTime = sdVo.getClientTranslateTime() + res.getSumCount() * res.getClientTransTime();
					Float serverTranslateTime = sdVo.getServerTranslateTime() + res.getSumCount() * res.getServerTransTime();
					res.setSumCount(sumCount);
					res.setSuccessCount(successCount);
					res.setHealthCount(healthCount);
					res.setAverageNetTransTime(sumNetTime / sumCount);
					res.setClientTransTime(clientTranslateTime / sumCount);
					res.setServerTransTime(serverTranslateTime / sumCount);
					List<String> resourceCodes = res.getResourceCodes();
					if (StringUtils.isNotEmpty(sdVo.getTableName())) {
						resourceCodes.add(sdVo.getTableName() + "#" + sdVo.getId());
					} else {
						resourceCodes.add(sdVo.getId());
					}
					res.setResourceCodes(resourceCodes);
					res.setStartTimeUsec(res.getStartTimeUsec() >= sdVo.getStartTimeUsec() ? sdVo.getStartTimeUsec() : res.getStartTimeUsec());
					res.setEndTimeUsec(res.getEndTimeUsec() >= sdVo.getEndTimeUsec() ? res.getEndTimeUsec() : sdVo.getEndTimeUsec());

					res.setServerResponseStartTimeUsec(res.getServerResponseStartTimeUsec() >= sdVo.getServerResponseStartTimeUsec()
							? sdVo.getServerResponseStartTimeUsec() : res.getServerResponseStartTimeUsec());
					res.setServerResponseEndTimeUsec(res.getServerResponseEndTimeUsec() >= sdVo.getServerResponseEndTimeUsec()
							? res.getServerResponseEndTimeUsec() : sdVo.getServerResponseEndTimeUsec());
					// 合并响应时长
					res.setServerResponseTime((float) mergeRange(res.getMergeTimeRangeList(),
							sdVo.getServerResponseStartTimeUsec(), sdVo.getServerResponseEndTimeUsec()));

					res.setStartTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(res.getStartTimeUsec() / 1000));
					res.setEndTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(res.getEndTimeUsec() / 1000));
					res.setServerResponseStartTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(res.getServerResponseStartTimeUsec() / 1000));
					res.setServerResponseEndTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(res.getServerResponseEndTimeUsec() / 1000));
					res.setSuccessRate(Math.round((float) healthCount / sumCount * 10000) / 100f);
					res.setOneFlag(0);
					sdVoMap.put(segmentId, res);
				} else {
					res = new SegmentDetail();
					res.setSegmentId(segmentId);
					res.setDealTime((float) sdVo.getDealTime());
					res.setServerResponseTime((float) sdVo.getServerResponseTime());
					res.setAverageNetTransTime(sdVo.getNetTime());
					Integer c = sdVo.getState() == 1 ? 1 : 0;
					res.setSuccessCount(c);
					res.setHealthCount(c);
					res.setSumCount(1L);
					res.setResponseMac(sdVo.getResponseMac());
					List<String> codes = new ArrayList<>();
					if (StringUtils.isNotEmpty(sdVo.getTableName())) {
						codes.add(sdVo.getTableName() + "#" + sdVo.getId());
					} else {
						codes.add(sdVo.getId());
					}
					res.setResourceCodes(codes);
					res.setStartTimeUsec(sdVo.getStartTimeUsec());
					res.setEndTimeUsec(sdVo.getEndTimeUsec());
					res.setStartTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(sdVo.getStartTimeUsec() / 1000));
					res.setEndTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(sdVo.getEndTimeUsec() / 1000));
					res.setClientTransTime(sdVo.getClientTranslateTime());
					res.setServerTransTime(sdVo.getServerTranslateTime());
					res.setServerResponseStartTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(sdVo.getServerResponseStartTimeUsec() / 1000));
					res.setServerResponseEndTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(sdVo.getServerResponseEndTimeUsec() / 1000));

					// 第一次合并响应时长
					List<Map<String, Object>> mergeTimeRangeList = new ArrayList<>();
					mergeRange(mergeTimeRangeList, sdVo.getServerResponseStartTimeUsec(), sdVo.getServerResponseEndTimeUsec());
					res.setMergeTimeRangeList(mergeTimeRangeList);

					res.setServerResponseStartTimeUsec(sdVo.getServerResponseStartTimeUsec());
					res.setServerResponseEndTimeUsec(sdVo.getServerResponseEndTimeUsec());
					res.setDataType(sdVo.getDataType());
					res.setIpPort(sdVo.getResponseIp() + ":" + sdVo.getResponsePort());
					res.setSuccessRate(Math.round((float) sdVo.getState() / 1 * 10000) / 100f);
					res.setOneFlag(1);
					sdVoMap.put(segmentId, res);
				}
			}
		}
		return sdVoMap;
	}

	/**
	 * 根据id查询业务步骤
	 *
	 * @param id       业务步骤id
	 * @param detailId 详单ID
	 * @return 业务步骤
	 */
	private BusinessStep queryBusinessStepById(String id, String detailId) {
		Long detailIdTemp = null;
		if (StringUtils.isNotEmpty(detailId)) {
			detailIdTemp = Long.valueOf(detailId);
		}
		//根据步骤ID查询步骤详情
		Map<String, Object> map = stepDetailMapper.selectStepDetail(Long.valueOf(id), detailIdTemp);
		BusinessStep businessStep = null;
		if (map != null && map.size() > 0) {
			businessStep = new BusinessStep();
			//ID(主键)
			businessStep.setId(DataTypeUtil.objectToString(map.get("id")));
			//会话是否完成(0:完整1:无响应2:请求不完整3:响应不完整)
			businessStep.setIsCompletion(DataTypeUtil.objectToInteger(map.get("isCompletion")));
			//端到端关联标记
			//businessStep.setRelvanceFlag(relvanceFlag);
			//业务资源编码
			businessStep.setResourceCode(DataTypeUtil.objectToString(map.get("resourceCode")));
			//步骤1表的数据ID
			businessStep.setBusinessStepId(DataTypeUtil.objectToString(map.get("id")));
			//业务详单ID
			businessStep.setBusinessDetailId(DataTypeUtil.objectToString(map.get("businessDetailId")));
			//用户SESSION
			businessStep.setSessionId(DataTypeUtil.objectToString(map.get("sessionId")));
			//用户唯一标识
			businessStep.setUserIdentify(DataTypeUtil.objectToString(map.get("userIdentify")));
			//业务系统ID
			businessStep.setSystemId(DataTypeUtil.objectToInteger(map.get("systemId")));
			//业务系统名称
			businessStep.setSystemName(DataTypeUtil.objectToString(map.get("systemName")));
			//网段ID
			businessStep.setSegmentId(DataTypeUtil.objectToInteger(map.get("segmentId")));
			//网段名称
			businessStep.setSegmentName(DataTypeUtil.objectToString(map.get("segmentName")));
			//业务编码
			businessStep.setBusinessCode(DataTypeUtil.objectToString(map.get("businessCode")));
			//业务名称
			businessStep.setBusinessName(DataTypeUtil.objectToString(map.get("businessName")));
			//业务步骤编码
			businessStep.setStepCode(DataTypeUtil.objectToString(map.get("stepCode")));
			//业务步骤名称
			businessStep.setStepName(DataTypeUtil.objectToString(map.get("stepName")));
			//步骤类型
			//businessStep.setStepType();
			//业务访问URL
			businessStep.setRequestUrl(DataTypeUtil.objectToString(map.get("requestUrl")));
			//业务原始访问URL
			businessStep.setOrlUrl(DataTypeUtil.objectToString(map.get("oriUrl")));
			//加载项关联URL
			businessStep.setReferer(DataTypeUtil.objectToString(map.get("referer")));
			//操作系统
			businessStep.setOperatingSystem(DataTypeUtil.objectToString(map.get("operatingSystem")));
			//浏览器
			businessStep.setBrowser(DataTypeUtil.objectToString(map.get("browser")));
			//区域编码
			businessStep.setRegionalCode(DataTypeUtil.objectToString(map.get("regionalCode")));
			//业务处理状态,操作成功标识
			businessStep.setDealState(DataTypeUtil.objectToInteger(map.get("dealState")));
			//告警数量
			businessStep.setAlarmCount(DataTypeUtil.objectToInteger(map.get("alarmCount")));
			//统计指定告警类型的告警总数
			businessStep.setCustomizedAlarmCount(DataTypeUtil.objectToInteger(map.get("customizedAlarmCount")));
			//统计业务是否由于指定类型告警而产生的告警访问量(ES详单表字段customizedAlarmCount > 0 时，该字段 = 1)
			if (businessStep.getCustomizedAlarmCount() > 0) {
				businessStep.setCustomizedAlarmVisitCount(1);
			}
			//系统返回码
			businessStep.setServerResCode(DataTypeUtil.objectToInteger(map.get("serverResCode")));
			//开始时间（Date:YYYY-MM-DD HH:MM:SS）
			businessStep.setStartTime(YDateUtil.stringFormatDate(DataTypeUtil.objectToString(map.get("startTime"))));
			//开始时间（Long:微妙）
			businessStep.setStartTimeUsec(dateToTimestamp((LocalDateTime) map.get("startTime")));
			//结束时间（Date:YYYY-MM-DD HH:MM:SS）
			businessStep.setStartTime(YDateUtil.stringFormatDate(DataTypeUtil.objectToString(map.get("endTime"))));
			//结束时间（Long:微妙）
			businessStep.setEndTimeUsec(dateToTimestamp((LocalDateTime) map.get("endTime")));
			//处理时长
			businessStep.setDealTime(DataTypeUtil.objectToLong(map.get("dealTime")));
			//客户端空闲时间（详单组合后计算出与上一个步骤的客户操作间隔时间）
			//private Long 	clientFreeTime;
			//businessStep.setClientFreeTime(DataTypeUtil.objectToLong(map.get("dealTime")));
			//客户端传输时长
			businessStep.setClientTranslateTime(DataTypeUtil.objectToLong(map.get("clientTranslateTime")));
			//服务器响应开始时间（Date:YYYY-MM-DD HH:MM:SS）
			businessStep.setServerResponseStartTime(YDateUtil.stringFormatDate(DataTypeUtil.objectToString(map.get("serverResponseStartTime"))));
			//服务器响应开始时间（Long:微妙）
			businessStep.setServerResponseStartTimeUsec((Long) map.get("serverResponseStartTimeUsec"));
			//服务器响应结束时间（Date:YYYY-MM-DD HH:MM:SS）
			businessStep.setServerResponseEndTime(YDateUtil.stringFormatDate(DataTypeUtil.objectToString(map.get("serverResponseEndTimeUsec"))));
			//服务器响应结束时间（Long:微妙）
			businessStep.setServerResponseEndTimeUsec((Long) map.get("serverResponseEndTimeUsec"));
			//服务器响应时间
			businessStep.setServerResponseTime(DataTypeUtil.objectToLong(map.get("serverResponseTime")));
			//服务器传输时长
			businessStep.setServerTranslateTime(DataTypeUtil.objectToLong(map.get("serverTranslateTime")));
			//客户端请求IP
			businessStep.setRequestIp(DataTypeUtil.objectToString(map.get("requestIp")));
			//客户端请求端口
			businessStep.setRequestPort(DataTypeUtil.objectToInteger(map.get("requestPort")));
			//服务器响应IP
			businessStep.setResponseIp(DataTypeUtil.objectToString(map.get("responseIp")));
			//服务器响应端口
			businessStep.setResponsePort(DataTypeUtil.objectToInteger(map.get("responsePort")));
			//是否被告警
			businessStep.setIsAlarm(DataTypeUtil.objectToInteger(map.get("isAlarm")));
			//提示语
			businessStep.setResultExplain(DataTypeUtil.objectToString(map.get("resultExplain")));
			//自定义返回码
			businessStep.setCustomResCode(DataTypeUtil.objectToString(map.get("customResCode")));
			//营业厅编码
			businessStep.setBusinessHall(DataTypeUtil.objectToString(map.get("businessHall")));
			//营业员编码
			businessStep.setOperatorId(DataTypeUtil.objectToString(map.get("operatorId")));
			//请求mac
//    		businessStep.setRequestMac(DataTypeUtil.objectToString(map.get("requestMac")));
			businessStep.setRequestMac(DataTypeUtil.objectToString(map.get("requestMac")));
			//响应mac
//    		businessStep.setResponseMac(DataTypeUtil.objectToString(map.get("responseMac")));
			businessStep.setResponseMac(DataTypeUtil.objectToString(map.get("responseMac")));
			//专属指标
			businessStep.setExclusiveIndicator(DataTypeUtil.objectToString(map.get("exclusiveIndicator")));
		}
		return businessStep;
	}

	/**
	 * 根据首端数据进行查询
	 *
	 * @param headBs 首端
	 * @return
	 */
	private Map<Integer, Map<String, List<SegmentDataVo>>> queryRelevanceData(BusinessStep headBs, EndToEndConfig config) {
		// 1、获取容错时间，毫秒转换为微秒
		String taskTitle = "[端到端实时关联-" + UUID.randomUUID().toString().replaceAll("-", "").toUpperCase() + "]";
		Long containTime = config.getContainTime() * 1000L;
		log.info("{}_4: 容错时间：{}微妙", taskTitle, containTime);

		// 2、设置查询条件，开始时间和结束时间包含在容错范围内
		QuerySegmentVo querySegmentVo = new QuerySegmentVo();
		querySegmentVo.setResourceCode(headBs.getResourceCode());
		querySegmentVo.setServerResponseStartTimeUsec(headBs.getServerResponseStartTimeUsec() - containTime);
		querySegmentVo.setServerResponseEndTimeUsec(headBs.getServerResponseEndTimeUsec() + containTime);
		querySegmentVo.setStartTimeUsec(headBs.getStartTimeUsec() - containTime);
		querySegmentVo.setEndTimeUsec(headBs.getEndTimeUsec() + containTime);

		/*
		 * 3、从首端出发(如：资源编码_端id  c55729b16b7e7f2c_4)在缓存中(SegmentResourceRelationshipMap，
		 *    该缓存是系统启动时加载的端到端关联t_single_resource_it_relationship表中的数据)递归查找后端(资源编码_端id)
		 * 大端类型：资源编码集合
		 */
		Map<Integer, List<String>> segmentTypeResourceCodeMap = config.getSegmentTypeResourceCodeMap();
		if (segmentTypeResourceCodeMap.size() <= 0) {
			log.info("{}_4: 后端资源不存在，首端资源编码：{}, 首端id：{}", taskTitle, headBs.getResourceCode(), headBs.getSegmentId());
		}

		/*
		 * 4、根据大端类型和查询条件，多线程查询数据，类型为1查询business_web_xxx，类型为2查询business_mid_xxx，
		 *    类型为3查询business_db_xxx
		 * 大端类型：(资源编码：网端数据)
		 */
		return parallelQueryData(segmentTypeResourceCodeMap, querySegmentVo);
	}

	/**
	 * 设置客户端和首端数据
	 *
	 * @param headBs 首端步骤
	 * @return 端id与端详情vo对象的映射关系
	 */
	private Map<Integer, SegmentDetail> fillHeadData(BusinessStep headBs) {
		Map<Integer, SegmentDetail> map = new HashMap<>();

		// 1、设置首端
		Integer segmentId = headBs.getSegmentId();
		SegmentDetail head = new SegmentDetail();
		// 数据id
		head.setDataId(headBs.getId());
		// 数据类型
		head.setDataType("step");
		// 网端id
		head.setSegmentId(segmentId);
		// 处理时长
		head.setDealTime((float) headBs.getDealTime());
		// 服务器响应时长
		head.setServerResponseTime((float) headBs.getServerResponseTime());
		// 成功数(根据业务处理状态判断)
		head.setSuccessCount(headBs.getDealState() == 1 ? 1 : 0);
		// IP端口
		head.setIpPort(headBs.getResponseIp() + ":" + headBs.getResponsePort());
		// 首端标识
		head.setHeaderFlag(1);
		// 开始时间
		head.setStartTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(headBs.getStartTimeUsec() / 1000));
		// 结束时间
		head.setEndTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(headBs.getEndTimeUsec() / 1000));
		// 服务器响应开始时间
		head.setServerResponseStartTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(headBs.getServerResponseStartTimeUsec() / 1000));
		// 服务器响应结束时间
		head.setServerResponseEndTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(headBs.getServerResponseEndTimeUsec() / 1000));
		// 服务器响应开始时间戳
		head.setServerResponseStartTimeUsec(headBs.getServerResponseStartTimeUsec());
		// 服务器响应结束时间戳
		head.setServerResponseEndTimeUsec(headBs.getServerResponseEndTimeUsec());
		// 客户端传输时长
		head.setClientTransTime((float) headBs.getClientTranslateTime());
		// 服务端传输时长
		head.setServerTransTime((float) headBs.getServerTranslateTime());
		// 响应mac
		head.setResponseMac(headBs.getResponseMac());
		map.put(segmentId, head);

		// 2、设置客户端
		SegmentDetail client = new SegmentDetail();
		// 数据id
		client.setDataId(headBs.getId());
		// 数据类型
		client.setDataType("step");
		// 网端id
		client.setSegmentId(0);
		// 处理时长
		client.setDealTime((float) headBs.getDealTime());
		// 成功数(根据业务处理状态判断)
		client.setSuccessCount(headBs.getDealState() == 1 ? 1 : 0);
		// IP端口
		client.setIpPort(headBs.getRequestIp() + ":" + headBs.getRequestPort());
		// 开始时间
		client.setStartTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(headBs.getStartTimeUsec() / 1000));
		// 结束时间
		client.setEndTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(headBs.getEndTimeUsec() / 1000));
		// 客户端传输时长
		client.setClientTransTime((float) headBs.getClientTranslateTime());
		// 平均网络传输耗时
		client.setAverageNetTransTime((float) (headBs.getClientTranslateTime() + headBs.getServerTranslateTime()) / 1000);
		// 请求mac
		client.setRequestMac(headBs.getRequestMac());
		map.put(0, client);
		return map;
	}

	/**
	 * 并行获取数据
	 *
	 * @param segmentTypeResourceCodeMap 端类型资源编码映射
	 * @param querySegmentVo             查询条件
	 * @return 以大端分类的网端数据结果集
	 */
	private Map<Integer, Map<String, List<SegmentDataVo>>> parallelQueryData(
			Map<Integer, List<String>> segmentTypeResourceCodeMap, QuerySegmentVo querySegmentVo) {
		String taskTitle = "[端到端实时关联-" + UUID.randomUUID().toString().replaceAll("-", "").toUpperCase() + "]";
		//是否多线程查询
		boolean isMultithread = true;
		try {
			String multithread = ParamResolver.getStr(ConfigKey.E2E_MULTITHREAD);
			if (ObjectUtil.isNotEmpty(multithread)) {
				isMultithread = Integer.valueOf(multithread) == 1;
			}
		} catch (Exception e) {
			log.error("{}读取多线程配置错误，执行多线程查询数据", e.getMessage());
		}
		// 多线程查询结果集暂存map
		Map<Integer, List<SegmentDataVo>> tempMap = new HashMap<>();
		if (isMultithread) {
			log.info("{}_多线程查询数据开始---------------", taskTitle);
			CountDownLatch countDownLatch = new CountDownLatch(segmentTypeResourceCodeMap.size());
			// 1、多线程查询数据
			segmentTypeResourceCodeMap.forEach((type, codeList) -> {
				if (!tempMap.containsKey(type)) {
					tempMap.put(type, new ArrayList<>());
				}
				List<SegmentDataVo> tempList = tempMap.get(type);
				threadPool.execute(() -> {
					try {
						if (ObjectUtil.isNotEmpty(type) && ObjectUtil.isNotEmpty(codeList)) {
							// 1、设置表前缀、表类型
							String tableNamePre = "", dataType = "";
							if (type == 1) {
								// 查询business_web_xxx
								tableNamePre = "dwd_business_web";
								dataType = "web";
							} else if (type == 2) {
								// 查询business_mid_xxx
								tableNamePre = "dwd_business_mid";
								dataType = "mid";
							} else if (type == 3) {
								// 查询business_db_xxx
								tableNamePre = "dwd_business_db";
								dataType = "db";
							}
							if (ObjectUtil.isNotEmpty(tableNamePre)) {
								// 2、获取表全名称
								IPage page = new Page(1, -1);
								log.info("{}-开始查询{}表数据，条件：code:[{}],startTime:{}， endTime:{}", taskTitle, tableNamePre, codeList.toString(), querySegmentVo.getServerResponseStartTimeUsec(), querySegmentVo.getServerResponseEndTimeUsec());
								List<SegmentDataVo> segmentDataVoList = businessDetailMapper.queryNodeMsg(page, tableNamePre, codeList, querySegmentVo.getServerResponseStartTimeUsec(), querySegmentVo.getServerResponseEndTimeUsec());
								if (segmentDataVoList == null || segmentDataVoList.size() == 0) {
									log.info("{}_4 查询{}相关表无数据", taskTitle, tableNamePre);
								}
								log.info("{}_查询{}表结束，共有{}条数据！", taskTitle, tableNamePre, segmentDataVoList.size());
								// 4、处理结果
								for (SegmentDataVo vo : segmentDataVoList) {
									vo.setTableName(tableNamePre);
									vo.setDataType(dataType);
									vo.setState(vo.getDealState() != null ? Integer.parseInt(vo.getDealState() + "") : 0);
									vo.setNetTime(vo.getClientTranslateTime() + vo.getServerTranslateTime());
									tempList.add(vo);
								}
							}
						}
					} finally {
						// 保证不会被阻塞
						countDownLatch.countDown();
					}
				});
			});
			// 2、阻塞当前线程
			try {
				countDownLatch.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
				log.error("{}_阻塞异常", taskTitle);
			}
			log.info("{}_多线程查询数据结束---------------", taskTitle);
		} else {//单线程
			log.info("{}_单线程查询数据开始---------------", taskTitle);
			segmentTypeResourceCodeMap.forEach((type, codeList) -> {
				if (!tempMap.containsKey(type)) {
					tempMap.put(type, new ArrayList<>());
				}
				List<SegmentDataVo> tempList = tempMap.get(type);
				// 1、设置表前缀、表类型
				String tableNamePre = "", dataType = "";
				if (type == 1) {
					// 查询business_web_xxx
					tableNamePre = "dwd_business_web";
					dataType = "web";
				} else if (type == 2) {
					// 查询business_mid_xxx
					tableNamePre = "dwd_business_mid";
					dataType = "mid";
				} else if (type == 3) {
					// 查询business_db_xxx
					tableNamePre = "dwd_business_db";
					dataType = "db";
				}
				if (ObjectUtil.isNotEmpty(tableNamePre)) {
					// 2、获取表全名称
					IPage page = new Page(1, -1);
					log.info("{}-开始查询{}表数据，条件：code:[{}],startTime:{}， endTime:{}", taskTitle, tableNamePre, codeList.toString(), querySegmentVo.getServerResponseStartTimeUsec(), querySegmentVo.getServerResponseEndTimeUsec());
					List<SegmentDataVo> segmentDataVoList = businessDetailMapper.queryNodeMsg(page, tableNamePre, codeList, querySegmentVo.getServerResponseStartTimeUsec(), querySegmentVo.getServerResponseEndTimeUsec());
					if (segmentDataVoList == null || segmentDataVoList.size() == 0) {
						log.info("{}_4 查询{}相关表无数据", taskTitle, tableNamePre);
					}
					log.info("{}_查询结束，共有{}条数据！", taskTitle, segmentDataVoList.size());
					// 4、处理结果
					for (SegmentDataVo vo : segmentDataVoList) {
						vo.setTableName(tableNamePre);
						vo.setDataType(dataType);
						vo.setState(vo.getDealState() != null ? Integer.parseInt(vo.getDealState() + "") : 0);
						vo.setNetTime(vo.getClientTranslateTime() + vo.getServerTranslateTime());
						tempList.add(vo);
					}
				}
			});
			log.info("{}_单线程查询数据结束---------------", taskTitle);
		}

		// 3、放行后处理结果集
		//map<type,<resourceCode,端数据>>
		Map<Integer, Map<String, List<SegmentDataVo>>> result = new HashMap<>();
		tempMap.forEach((type, list) -> {
			Map<String, List<SegmentDataVo>> resourceCodeMap = new HashMap<>();
			list.forEach(sdVo -> {
				String resourceCode = sdVo.getResourceCode();
				if (resourceCodeMap.containsKey(resourceCode)) {
					resourceCodeMap.get(resourceCode).add(sdVo);
				} else {
					List<SegmentDataVo> l = new ArrayList<>();
					l.add(sdVo);
					resourceCodeMap.put(resourceCode, l);
				}
			});
			result.put(type, resourceCodeMap);
		});
		return result;
	}

	/**
	 * 端到端关联递归筛选、数据补全
	 *
	 * @return 端详情vo对象集合
	 */
	private List<SegmentDataVo> recursiveScreen(
			BusinessStep step,
			Map<Integer, Map<String, List<SegmentDataVo>>> allSegmentData,
			EndToEndConfig config,
			List<String> distinctList,
			List<SegmentDataVo> result,
			boolean insertFlag) {
		String taskTitle = "[端到端实时关联-" + UUID.randomUUID().toString().replaceAll("-", "").toUpperCase() + "]";
		// 服务资源
		String resourceCode = step.getResourceCode();
		// 端id
		Integer segmentId = step.getSegmentId();

		// 1、在缓存SegmentResourceRelationshipMap中取后端资源
		Map<String, List<EndToEndNodeVo>> resourceNodes = config.getResourceNodes();
		List<EndToEndNodeVo> endToEndNodeVos = resourceNodes.get(resourceCode);
		// 过滤没有后端资源的数据
		if (ObjectUtil.isEmpty(endToEndNodeVos)) {
			log.info("{}_4 缓存中无后端资源，前端资源编码：{}", taskTitle, resourceCode);
			return result;
		}
		// 2、遍历后端资源
		// 备用数据
		SegmentDataVo insertData = null;
		// 补全数据时的大端类型，决定补全到哪个表中
		Integer insertSegmentType = 0;
		// 关联到后端数据标识
		boolean reFlag = false;
		// 端id与该端是否NAT、该端网络类型的映射
		Map<Integer, SegmentIdMappingVo> segmentIdMap = config.getSegmentIdMap();
		for (EndToEndNodeVo afterNode : endToEndNodeVos) {
			ConfSegment segmentInfo = afterNode.getSegmentInfo();
			if (ObjectUtil.isEmpty(segmentInfo)) {
				continue;//todo 配置接口有点问题，先兼容一下
			}
			List<EndToEndNodeVo> afterNodes = resourceNodes.get(resourceCode);//当前遍历节点的后端节点
			// 2.1、先过滤服务资源一样且端一样的后端资源

			// 2.2、根据识别字符类型取端详细数据并判断数据是否存在，过滤无数据的后端资源
			Map<String, List<SegmentDataVo>> allSegmentTypeData = allSegmentData.get(segmentInfo.getType());
			if (CollectionUtils.isEmpty(allSegmentTypeData)) {
				log.info("{}_4 无大端类型为{}的结果集(1:web 2:mid 3:db)", taskTitle, segmentInfo.getType());
				return result;
			}
			List<SegmentDataVo> segmentDataVoList = allSegmentTypeData.get(afterNode.getResourceCode());
			if (CollectionUtils.isEmpty(segmentDataVoList)) {
				log.info("{}_4 在结果集中，该后端服务资源无数据，编码：{}", taskTitle, afterNode.getResourceCode());
				continue;
			}
			// 2.3、如果是负载转发的(网络类型为1)，将开始时间给响应开始时间
			SegmentIdMappingVo segmentIdMappingVo = segmentIdMap.get(segmentId);
			if (segmentIdMappingVo.getNetworkType() == 1) {
				step.setServerResponseStartTimeUsec(step.getStartTimeUsec());
			}
			// 2.4、遍历端详细数据，挑选出端数据，已按响应时长倒序
			Long preServerResponseStartTimeUsec = step.getServerResponseStartTimeUsec() - config.getContainTime();
			Long afterServerResponseStartTimeUsec = step.getServerResponseEndTimeUsec() + config.getContainTime();
			Integer isNat = segmentIdMappingVo.getIsNat();
			List<SegmentDataVo> screenList = new ArrayList<>();
			for (SegmentDataVo segmentDataVo : segmentDataVoList) {
				for (EndToEndNodeVo node : afterNodes) {
					ConfSegment afterSegmentInfo = node.getSegmentInfo();
					String afterResourceCode = segmentDataVo.getResourceCode();
					Integer afterSegmentId = segmentDataVo.getSegmentId();
					// 去重
					if (distinctList.contains(afterResourceCode + afterSegmentId)) {
						continue;
					}

					// 2.4.1、如果后端id一致，先记录备用数据，用于后续补全关联不上的端
					if (afterSegmentId.equals(node.getSegmentId())) {
						insertData = segmentDataVo;
						insertSegmentType = afterSegmentInfo.getType();
					}
					// 2.4.2、满足以下条件，即认为该端是关联的
					// 1)、后端id一致
					if (afterSegmentId.equals(node.getSegmentId())) {
						// 2)、响应开始时间和响应结束时间包含在容错时间范围内
						if (segmentDataVo.getServerResponseStartTimeUsec() >= preServerResponseStartTimeUsec
								&& segmentDataVo.getServerResponseEndTimeUsec() <= afterServerResponseStartTimeUsec) {
							// 3)、ip是关联的(前一端响应IP等于后一端的请求IP)或nat等于1
							if (isNat == 1 || step.getResponseIp().equals(segmentDataVo.getRequestIp())) {
								// 4)、专属指标是关联的
								if (exclusivesConnect(step.getExclusiveIndicator(), step.getResourceCode(),
										segmentDataVo.getExclusiveIndicator(), segmentDataVo.getResourceCode())) {
									log.info("{}_4 资源：{},关联成功，网端id：{}", taskTitle, afterResourceCode, afterSegmentId);
									distinctList.add(afterResourceCode + afterSegmentId);
									screenList.add(segmentDataVo);
								} else {
									log.info("{}_4 关联失败，专属指标不是关联的", taskTitle);
								}
							} else {
								log.info("{}_4 关联失败，ip不是关联的，前一端响应IP：{},后一端请求IP：{}", taskTitle, step.getResponseIp(), segmentDataVo.getRequestIp());
							}
						} else {
							log.info("_4 关联失败，响应开始时间(" + segmentDataVo.getServerResponseStartTimeUsec()
									+ ")和响应结束时间(" + segmentDataVo.getServerResponseEndTimeUsec() + ")不包含在容错时间范围["
									+ preServerResponseStartTimeUsec + ", " + afterServerResponseStartTimeUsec + "]内");
						}
					} else {
						log.info("_4 关联失败，后端id不一致，" + afterSegmentId + " 不等于 " + node.getSegmentId());
					}
				}
			}

			// 3、判断是否筛选出端数据
			if (CollectionUtils.isEmpty(screenList)) {
				// 没有筛选出符合条件的端数据，根据后续的条件决定是否需要补全数据
				continue;
			}

			// 4、在挑出的端数据中找出在开始和结束时间内的(不包括容错时间)
			List<SegmentDataVo> fTimeList = screenList.stream()
					.filter(item -> item.getServerResponseStartTimeUsec() >= step.getServerResponseStartTimeUsec()
							&& item.getServerResponseEndTimeUsec() <= step.getServerResponseEndTimeUsec()
							&& item.getStartTimeUsec() >= step.getServerResponseStartTimeUsec()
							&& item.getEndTimeUsec() <= step.getServerResponseEndTimeUsec())
					.collect(Collectors.toList());
			if (CollectionUtils.isEmpty(fTimeList)) {
				// 4.1、如果存在这样的端数据，改时间前端能包含得上，改专属指标和前端的一样，然后递归
				for (SegmentDataVo screenSegmentDataVo : screenList) {
					String exclusive = exclusivesFrontToBack(step.getExclusiveIndicator(), step.getResourceCode(),
							screenSegmentDataVo.getExclusiveIndicator());
					if (StringUtils.isNotEmpty(exclusive) && !"0".equals(exclusive)) {
						screenSegmentDataVo.setExclusiveIndicator(exclusive);
					}
					// 添加到结果集
					result.add(screenSegmentDataVo);
					// 递归
					for (EndToEndNodeVo node : afterNodes) {
						BusinessStep bs = BeanUtils.convert(screenSegmentDataVo, BusinessStep.class);
						bs.setResourceCode(node.getResourceCode());
						recursiveScreen(bs, allSegmentData, config, distinctList, result, false);
					}
					// 关联到后端数据标识为true
					reFlag = true;
					// 补全数据设置为null
					insertData = null;
				}
			} else {
				// 4.2、如果存在容错时间外的端数据，且是补的数据，改专属指标和前端的一样，然后递归
				for (SegmentDataVo screenSegmentDataVo : fTimeList) {
					if (insertFlag) {
						String exclusive = exclusivesFrontToBack(step.getExclusiveIndicator(), step.getResourceCode(),
								screenSegmentDataVo.getExclusiveIndicator());
						if (StringUtils.isNotEmpty(exclusive) && !"0".equals(exclusive)) {
							screenSegmentDataVo.setExclusiveIndicator(exclusive);
						}
						// 4205屏蔽端到端关联数据修改的逻辑
						// updateSegmentInfo();
					}
					// 添加到结果集
					result.add(screenSegmentDataVo);
					// 递归
					for (EndToEndNodeVo node : afterNodes) {
						BusinessStep bs = BeanUtils.convert(screenSegmentDataVo, BusinessStep.class);
						bs.setResourceCode(node.getResourceCode());
						recursiveScreen(bs, allSegmentData, config, distinctList, result, false);
					}
					// 关联到后端数据标识为true
					reFlag = true;
					// 补全数据设置为null
					insertData = null;
				}
			}
		}

		// 5、根据开关决定是否进行数据补全
		if ("0".equals(config.getBusinessDetailInsertFlag())) {
			// 数据补全开关已关闭
			log.info("数据补全开关已关闭");
			return result;
		}

		// 6、判断该端是否满足数据补全条件
		if (step.getServerResCode() == 200
				&& step.getDealState() == 1
				&& !reFlag
				&& insertData != null
				&& (insertSegmentType == 1 || insertSegmentType == 2)
				&& (step.getServerResponseEndTimeUsec() - step.getServerResponseStartTimeUsec() > 1000L)) {
			Long serverResponseStartTimeUsec = step.getServerResponseStartTimeUsec();
			Long serverResponseEndTimeUsec = step.getServerResponseEndTimeUsec();
			Long timeUsec = serverResponseEndTimeUsec - serverResponseStartTimeUsec;
			Long start = serverResponseStartTimeUsec + timeUsec / 4;
			Long end = serverResponseEndTimeUsec - timeUsec / 4;
			insertData.setServerResponseStartTimeUsec(start);
			insertData.setServerResponseEndTimeUsec(end);
			insertData.setServerResponseStartTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(start / 1000));
			insertData.setServerResponseEndTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(end / 1000));
			insertData.setStartTimeUsec(start - timeUsec / 6);
			insertData.setEndTimeUsec(end + timeUsec / 6);
			insertData.setClientTranslateTimeo(timeUsec / 6);
			insertData.setServerTranslateTimeo(timeUsec / 6);
			insertData.setClientTranslateTime(timeUsec / 6 / 1000f);
			insertData.setServerTranslateTime(timeUsec / 6 / 1000f);
			insertData.setServerResponseTime(end - start);
			insertData.setDealTime(insertData.getEndTimeUsec() - insertData.getStartTimeUsec());
			insertData.setStartTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(insertData.getStartTimeUsec() / 1000));
			insertData.setEndTime(YDateUtil.yyyy_MM_dd_HH_mm_ssSSS(insertData.getEndTimeUsec() / 1000));
			insertData.setId(String.valueOf(Long.parseLong((new Date()).getTime() + "" + ((int) (Math.random() * 900) + 100))));
			if (segmentIdMap.get(segmentId).getIsNat() != 1) {
				insertData.setRequestIp(step.getRequestIp());
			}

			// 覆盖专属指标
			String exclusive = exclusivesFrontToBack(step.getExclusiveIndicator(), step.getResourceCode(),
					insertData.getExclusiveIndicator());
			if (StringUtils.isEmpty(exclusive) || "0".equals(exclusive)) {
				return result;
			}
			insertData.setExclusiveIndicator(exclusive);
			// 添加到去重集合
			distinctList.add(insertData.getResourceCode() + insertData.getSegmentId());

			result.add(insertData);
			// 从备用数据出发继续递归
			BusinessStep bs = BeanUtils.convert(insertData, BusinessStep.class);
			recursiveScreen(bs, allSegmentData, config, distinctList, result, true);
		}
		return result;
	}

	/**
	 * 合并时间范围
	 *
	 * @param list   合并集
	 * @param bStart 开始
	 * @param bEnd   结束
	 * @return 总时长
	 */
	private static long mergeRange(List<Map<String, Object>> list, Long bStart, Long bEnd) {
		// 1、初始化相关变量
		Long newStart = bStart, newEnd = bEnd;
		List<Integer> removeList = new ArrayList<>();
		List<Long> startList = new ArrayList<>();
		List<Long> endList = new ArrayList<>();
		startList.add(bStart);
		endList.add(bEnd);
		// 2、找出时间范围有交集的开始时间和结束时间，并记录当前索引
		int len = list.size();
		for (int i = 0; i < len; i++) {
			Map<String, Object> m = list.get(i);
			Long aStart = (Long) m.get("start");
			Long aEnd = (Long) m.get("end");
			// 判断时间范围是否有交集
			if ((aStart >= bStart && aStart <= bEnd)
					|| (bStart >= aStart && bStart <= aEnd)) {
				removeList.add(i);
				startList.add(aStart);
				endList.add(aEnd);
			}
		}
		// 3、从后往前删除(避免数组越界异常)记录的索引位置处的时间范围
		for (int i = removeList.size() - 1; i >= 0; i--) {
			list.remove(removeList.get(i).intValue());
		}
		// 4、计算交集中的最小开始时间和最大开始时间
		if (startList.size() > 1 && endList.size() > 1) {
			newStart = startList.stream().min(Comparator.naturalOrder()).get();
			newEnd = endList.stream().max(Comparator.naturalOrder()).get();
		}
		// 5、将新的时间范围添加到集合中
		Map<String, Object> newMap = new HashMap<>();
		newMap.put("start", newStart);
		newMap.put("end", newEnd);
		list.add(newMap);
		// 6、计算总时长
		long sum = list.stream().map(m -> {
			return (Long) m.get("end") - (Long) m.get("start");
		}).mapToLong(v -> v).sum();
		return sum;
	}

	/**
	 * 判断专属指标是否关联
	 *
	 * @param preEi           前服务资源专属指标字符串
	 * @param preResourceCode 前服务资源编码
	 * @param sufEi           后服务资源专属指标字符串
	 * @param sufResourceCode 后服务资源编码
	 * @return true为关联上，false为关联不上
	 */
	private boolean exclusivesConnect(String preEi, String preResourceCode, String sufEi, String sufResourceCode) {
		log.info("[端到端关联]：进入专属指标关联判断，前服务资编码：{}，后服务资源编码：{}，前专属指标串：{}，后专属指标串：{}", preResourceCode, sufResourceCode,preEi,sufEi);
		// 端到端关联专属指标配置集合
		Map<String, HashMap<String, Integer>> e2eConnectEiMap = remoteExclusiveIndicatorService.querySegmentConnectList().getData();

		// 1、关联信息为空时表示不需要关联
		if (CollectionUtils.isEmpty(e2eConnectEiMap) || StringUtils.isEmpty(preEi)) {
			// 不需要关联
			return true;
		}

		// 2、获取前后服务资源专属指标的配置
		HashMap<String, Integer> frontEiCfgMap = e2eConnectEiMap.get(preResourceCode);
		HashMap<String, Integer> backEiCfgMap = e2eConnectEiMap.get(sufResourceCode);
		if (CollectionUtils.isEmpty(frontEiCfgMap) || CollectionUtils.isEmpty(backEiCfgMap)) {
			// 从配置判断两个业务资源是否配了关联指标，如果没有配那么就不进行专属指标关联
			return true;
		}

		// 3、进行关联
		// 判断是否是需要专属指标关联
		boolean needConnect = false;
		// 是否关联成功
		boolean succeedFlag = false;
		// 前服务资源专属指标
		String[] frontEis = preEi.split("\\|");
		// 后服务资源专属指标
		String[] backEis = sufEi.split("\\|");
		out:
		for (String frontEi : frontEis) {
			// 3.1、前服务资源专属指标key、value要齐全
			String[] frontKeyValue = frontEi.split("=");
			if (frontKeyValue.length < 2) {
				continue;
			}
			// 3.2、配置中包含这个key
			String frontKey = frontKeyValue[0];
			String frontValue = frontKeyValue[1];
			if (!frontEiCfgMap.containsKey(frontKey)) {
				continue;
			}
			// 3.3、指标是后端关联指标或前后端关联指标
			Integer frontEiCfg = frontEiCfgMap.get(frontKey);
			if (frontEiCfg != 2 && frontEiCfg != 3) {
				continue;
			}
			// 3.4、需要关联
			needConnect = true;
			for (String backEi : backEis) {
				// 3.4.1、后服务资源专属指标key、value要齐全
				String[] backKeyValue = backEi.split("=");
				if (backKeyValue.length < 2) {
					continue;
				}
				String backKey = backKeyValue[0];
				String backValue = backKeyValue[1];
				// 3.4.2、前后服务资源编码是否一样
				if (preResourceCode.equals(sufResourceCode)) {
					if (frontValue.equalsIgnoreCase(backValue)) {
						// 前后端资源相同的无须判断指标类型
						succeedFlag = true;
						break out;
					}
				} else {
					// 配置中包含这个key
					if (!backEiCfgMap.containsKey(backKey)) {
						continue;
					}
					// 指标是前端关联指标或前后端关联指标
					Integer backEiCfg = backEiCfgMap.get(backKey);
					if (backEiCfg != 1 && backEiCfg != 3) {
						continue;
					}
					// 后端与前端相同的指标和值，认为关联成功
					if (frontValue.equalsIgnoreCase(backValue)) {
						succeedFlag = true;
						break out;
					}
				}
			}
		}
		// 需要关联但没关联上
		if (needConnect && !succeedFlag) {
			log.info("_4 需要关联专属指标，没关联上");
			log.info("前服务资源专属指标的配置：" + frontEiCfgMap);
			log.info("后服务资源专属指标的配置：" + backEiCfgMap);
			return false;
		}
		// 不需要关联或关联上了
		return true;
	}

	/**
	 * 专属指标的替换
	 *
	 * @param preEi           前服务资源专属指标字符串
	 * @param preResourceCode 前服务资源编码
	 * @param sufEi           后服务资源专属指标字符串
	 * @return 替换后的专属指标字符串
	 */
	private String exclusivesFrontToBack(String preEi, String preResourceCode, String sufEi) {
		// 端到端关联专属指标配置集合
		Map<String, HashMap<String, Integer>> e2eConnectEiMap = remoteExclusiveIndicatorService.querySegmentConnectList().getData();

		if (StringUtils.isEmpty(preEi)) {
			return sufEi;
		}

		HashMap<String, Integer> frontEiCfgMap = e2eConnectEiMap.get(preResourceCode);
		if (CollectionUtils.isEmpty(frontEiCfgMap)) {
			return sufEi;
		}

		// 前服务资源专属指标
		Map<String, String> map = new HashMap<>();
		String[] frontEis = preEi.split("\\|");
		for (String frontEi : frontEis) {
			String[] frontKeyValue = frontEi.split("=");
			if (frontKeyValue.length < 2) {
				// 关联指标值没取上来
				return "0";
			}
			String frontKey = frontKeyValue[0];
			String frontValue = frontKeyValue[1];
			if (StringUtils.isEmpty(frontValue)) {
				// 关联指标值没取上来
				return "0";
			}
			if (frontEiCfgMap.containsKey(frontKey)
					&& (frontEiCfgMap.get(frontKey) == 2 || frontEiCfgMap.get(frontKey) == 3)) {
				map.put(frontKey.toLowerCase(), frontValue);
			}
		}

		if (StringUtils.isEmpty(sufEi)) {
			return preEi;
		}
		// 后服务资源专属指标
		String[] backEis = sufEi.split("\\|");
		StringBuilder sb = new StringBuilder();
		for (String backEi : backEis) {
			String[] backKeyValue = backEi.split("=");
			String backKey = backKeyValue[0];
			String val = map.get(backKey.toLowerCase());
			if (StringUtils.isEmpty(val)) {
				sb.append(backKey).append("=").append(val);
			} else {
				sb.append(backEi);
			}
			sb.append("|");
		}
		return sb.toString();
	}

	/**
	 * 日期转换为时间戳
	 *
	 * @param dateStr 精确到纳秒的日期
	 * @return 返回精确到微秒的时间戳
	 */
	private long dateToTimestamp(LocalDateTime dateStr) {
		long time = Timestamp.valueOf(dateStr).getTime();
		return time * 1000;
	}

}
